/******************************************************************************
 * Copyright (C) 2015  671643387@qq.com
 *
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License as published by the
 * Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
 * more details.
 *
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 *****************************************************************************/

#include "file_win.h"
#include "node/util/string.h"
#include "node/fs/filesystem.h"
#include <cassert>
using FileSystem = ::node::fs::FileSystem;

namespace node {
    namespace fs {

        File::File(void)
            : handle_(INVALID_HANDLE_VALUE)
            , errno_(0)
            , errmsg_("")
        {

        }

        File::File(const char *filename, int openmode /* = OM_CREAT | OM_READ | OM_WRITE */, int flags /* = 0 */)
        {
            Open(filename, openmode, flags);
        }

        File::~File(void)
        {
            Close();
        }

        bool File::Open(const char *filename, int openmode /* = OM_CREAT | OM_READ | OM_WRITE */, int flags /* = 0 */)
        {
            assert(handle_ == INVALID_HANDLE_VALUE);

            DWORD dwAccess = 0;
            DWORD dwCreationDisposition = 0;
            DWORD dwFlags = 0;

            if (openmode & OM_READ) {
                dwAccess |= GENERIC_READ;
            }
            if (openmode & OM_WRITE) {
                dwAccess |= GENERIC_WRITE;
            }
            if (openmode & OM_CREAT) {
                dwCreationDisposition |= OPEN_ALWAYS;
            }
            if (openmode & OM_CREAT_ALWAYS) {
                dwCreationDisposition |= CREATE_ALWAYS;
            }
            if (openmode & OM_EXCL) {
                dwCreationDisposition |= OPEN_EXISTING;
            }
            if (openmode & OM_TRUNC) {
                dwCreationDisposition |= TRUNCATE_EXISTING;
            }
            
            if (flags & OF_NO_BUFFERING) {
                dwFlags |= FILE_FLAG_NO_BUFFERING;
            }

            filename_ = filename;
            if ((handle_ = ::CreateFileA(filename, dwAccess,
                FILE_SHARE_READ, NULL, dwCreationDisposition, dwFlags, NULL)) == INVALID_HANDLE_VALUE) {
                GenErrorInfo();
                return false;
            }
            return true;
        }

        void File::Close(void)
        {
            if (handle_ != INVALID_HANDLE_VALUE) {
                ::CloseHandle(handle_);
                handle_ = INVALID_HANDLE_VALUE;
            }
        }

        int File::Read(char *buffer, int length)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);
            if (handle_ != INVALID_HANDLE_VALUE)
            {
                DWORD bytes_read = 0;
                if (::ReadFile(handle_, buffer, (DWORD)length, &bytes_read, NULL)) {
                    return (int)bytes_read;
                }
                GenErrorInfo();
            }
            return -1;
        }

        int File::Write(const char *buffer, int length)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);
            if (handle_ != INVALID_HANDLE_VALUE)
            {
                DWORD bytes_write = 0;
                if (::WriteFile(handle_, buffer, (DWORD)length, &bytes_write, NULL)) {
                    return (int)bytes_write;
                }
                GenErrorInfo();
            }
            return -1;
        }

        int File::Seek(int offset, int method)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);
            if (handle_ != INVALID_HANDLE_VALUE)
            {
                DWORD dwMethod = FILE_BEGIN;
                if (method == SEEK_CUR) {
                    dwMethod = FILE_CURRENT;
                }
                else if (method == SEEK_END) {
                    dwMethod = FILE_END;
                }

                DWORD dwCurPos = ::SetFilePointer(handle_, offset, NULL, dwMethod);
                if (INVALID_SET_FILE_POINTER != dwCurPos) {
                    return (int)dwCurPos;
                }
                GenErrorInfo();
            }
            return -1;
        }

        bool File::Flush(void)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);
            if (handle_ != INVALID_HANDLE_VALUE)
            {
                if (::FlushFileBuffers(handle_)) { 
                    return true;
                }
                GenErrorInfo();
            }
            return false;
        }

        long int File::GetFileSize(void)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);
            return (long int)::GetFileSize(handle_, NULL);
        }

        bool File::GetTimes(struct tm *ctime, struct tm *mtime, struct tm *atime)
        {
            assert(handle_ != INVALID_HANDLE_VALUE);

            do
            {
                FILETIME c_time, m_time, a_time, l_time;
                SYSTEMTIME s_time;
                if (::GetFileTime(handle_, &c_time, &a_time, &m_time))
                {
                    if (ctime != NULL) {
                        if (::FileTimeToLocalFileTime(&c_time, &l_time)
                            && ::FileTimeToSystemTime(&l_time, &s_time))
                        {
                            ctime->tm_year = s_time.wYear - 1900;
                            ctime->tm_mon = s_time.wMonth - 1;
                            ctime->tm_mday = s_time.wDay;
                            ctime->tm_hour = s_time.wHour;
                            ctime->tm_min = s_time.wMinute;
                            ctime->tm_sec = s_time.wSecond;
                        }
                        else {
                            break;
                        }
                    }

                    if (mtime != NULL) {
                        if (::FileTimeToLocalFileTime(&m_time, &l_time)
                            && ::FileTimeToSystemTime(&l_time, &s_time))
                        {
                            mtime->tm_year = s_time.wYear - 1900;
                            mtime->tm_mon = s_time.wMonth - 1;
                            mtime->tm_mday = s_time.wDay;
                            mtime->tm_hour = s_time.wHour;
                            mtime->tm_min = s_time.wMinute;
                            mtime->tm_sec = s_time.wSecond;
                        }
                        else {
                            break;
                        }
                    }
                    if (atime != NULL) {
                        if (::FileTimeToLocalFileTime(&a_time, &l_time)
                            && ::FileTimeToSystemTime(&l_time, &s_time))
                        {
                            atime->tm_year = s_time.wYear - 1900;
                            atime->tm_mon = s_time.wMonth - 1;
                            atime->tm_mday = s_time.wDay;
                            atime->tm_hour = s_time.wHour;
                            atime->tm_min = s_time.wMinute;
                            atime->tm_sec = s_time.wSecond;
                        }
                        else {
                            break;
                        }
                    }

                    return true;
                }

            } while (false);

            return false;
        }

        bool File::Exists(const char *filename)
        {
            WIN32_FIND_DATA find_file_data;
            return ::FindFirstFile(filename, &find_file_data) != INVALID_HANDLE_VALUE;
        }

        void File::GenErrorInfo(void)
        {
            errno_ = (int)GetLastError();
            ::node::util::GetErrorMessage(errno_, errmsg_);
        }
    }
}